﻿using MTP.Utils;

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace MTP.Dispatch
{
    class DispatchClient : DispatchBase, IDispatchClient
    {
        private readonly SocketStream socket;

        private readonly AddressFamily addressFamily;

        private readonly NotifyHandler<Guid, MTPPackage> notifyHandler = new NotifyHandler<Guid, MTPPackage>();

        public Action<IDispatchClient> ConnectedEvent;

        public Action DisconnectEvent;

        public Action<MTPPackage> ClientReceiveEvent;

        private EndPoint endPoint;

        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        private int ReConnectTime = 0;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cipher">密码</param>
        public DispatchClient(byte[] cipher, AddressFamily addressFamily)
        {
            this.addressFamily = addressFamily;
            this.socket = new SocketStream(null, cipher, this); ;
        }
        public Task<IDispatchClient> ConnectAsync(IPAddress ipAddress, int port)
        {
            return ConnectAsync(ipAddress, port, CancellationToken.None);
        }

        public Task<IDispatchClient> ConnectAsync(string hostname, int port)
        {
            return ConnectAsync(hostname, port, CancellationToken.None);
        }

        public Task<IDispatchClient> ConnectAsync(EndPoint endPoint)
        {
            return ConnectAsync(endPoint, CancellationToken.None);
        }

        public Task<IDispatchClient> ConnectAsync(IPAddress ipAddress, int port, CancellationToken cancellationToken)
        {
            return ConnectAsync(new IPEndPoint(ipAddress, port), cancellationToken);
        }

        public Task<IDispatchClient> ConnectAsync(string hostname, int port, CancellationToken cancellationToken)
        {
            return ConnectAsync(new DnsEndPoint(hostname, port), cancellationToken);
        }

        public async Task<IDispatchClient> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken)
        {
            this.endPoint = endPoint;
            try
            {
                await ReLoad(cancellationToken);
            }
            catch (TaskCanceledException)
            {

            }
            return this;
        }

        private async Task ReLoad(CancellationToken cancellationToken)
        {
            try
            {
                await socket.Connect(Util.GetSocket(addressFamily), endPoint, cancellationTokenSource.Token);
                ConnectedEvent?.Invoke(this);
            }
            catch
            {
                if (ReConnectTime == 0)
                {
                    throw;
                }
                await TryReLoad(cancellationToken);
            }
        }

        private async Task TryReLoad(CancellationToken cancellationToken)
        {
            if (ReConnectTime > 0)
            {
                try
                {
                    await Task.Delay(ReConnectTime, cancellationToken);
                }
                catch (Exception)
                {
                    throw;
                }
                await ReLoad(cancellationToken);
            }
        }

        public IDispatchClient ReConnect(int outTime)
        {
            ReConnectTime = Math.Max(0, outTime * 1000);
            return this;
        }

        public override void Receive(Guid guid, MTPPackage package, string clientId)
        {
            if (!notifyHandler.Notify(guid, package))
                ClientReceiveEvent?.Invoke(package);
        }

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="package"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public Task<MTPPackage> SendAsync(MTPPackage package, int timeout)
        {
            var guid = Guid.NewGuid();
            return Task.Run(() =>
            {
                if (notifyHandler.WaitResult(guid, () => socket.Send(guid, package),
                out var result, timeout))
                {
                    return result.TryThrowException();
                }
                throw new TimeoutException("DispatchServer no response");
            });
        }

        public IDispatchClient OnConnected(Action<IDispatchClient> connected)
        {
            ConnectedEvent = connected;
            return this;
        }

        public IDispatchClient OnDisconnect(Action disconnect)
        {
            DisconnectEvent = disconnect;
            return this;
        }

        public IDispatchClient OnReceive(Action<MTPPackage> clientReceive)
        {
            ClientReceiveEvent = clientReceive;
            return this;
        }

        public override void Dispose(string clientId)
        {
            TryReLoad(cancellationTokenSource.Token).GetAwaiter();
            DisconnectEvent?.Invoke();
        }

        public void Dispose()
        {
            ReConnectTime = 0;
            if (!socket.IsClose)
            {
                cancellationTokenSource.Cancel();
                cancellationTokenSource.Dispose();
                socket.Dispose();
            }
        }

    }

}
